package algebra.rewriting.relational;

import option.KBVocabulary;
import kb.DescriptionLogicsKB;
import algebra.op.AlgebraicOP;
//import algebra.op.ConstantOP;
import algebra.op.ConstantOP;
import algebra.op.JoinOP;
import algebra.op.ProjectionOP;
import algebra.op.ScanOP;
import algebra.query.pd.PdConcept;
import algebra.query.pd.ProjDesc;
import algebra.rewriting.RewriteRule;

/**
 * Move projection inside. Currently we always use top? as the Pd General
 * version implemented: i.e., right op cna be any Q, not just ScanOP
 * 
 * @author j55wu
 * 
 */
public class NestedSearch extends RewriteRule {
	private final AlgebraicOP op;
	private static boolean general = true;

	public NestedSearch(AlgebraicOP op) {
		this.op = op;
	}

	public AlgebraicOP rewrite() {
		ProjectionOP ret = null;
		if (op instanceof ProjectionOP) {
			ProjectionOP po = (ProjectionOP) op;
			DescriptionLogicsKB kb = po.getPlanEvalKB();
			if (po.getSubquery() instanceof JoinOP) {
				JoinOP ip = po.getSubquery().asIntersectionOP();
				ScanOP si = null;
				if (!general) {
					si = nestedScanIntersected(ip, kb);
				} else {
					si = generalCase(ip, kb);
				}
				ret = new ProjectionOP(po.getPrjDesc(), si, kb);
				return ret;
			}
		}
		return null;
	}

	private ScanOP generalCase(JoinOP op, DescriptionLogicsKB kb) {
		ScanOP left = null;
		AlgebraicOP right = null;
		if (op.getFirstSubQuery() instanceof ScanOP) {
			left = op.getFirstSubQuery().asScanOP();
			right = op.getSecondSubQuery();
		} else if (op.getSecondSubQuery() instanceof ScanOP) {
			left = op.getSecondSubQuery().asScanOP();
			right = op.getFirstSubQuery();
		}
		if (left == null || right == null) {
			return null;
		}
		ProjDesc rep = null;
		rep = new PdConcept(KBVocabulary.CONCEPT_TOP);
		ProjectionOP inner = new ProjectionOP(rep, right, kb);
		AlgebraicOP co = left.getSubquery();
		boolean opt = false;
		if (co instanceof ConstantOP) {
			ConstantOP cp = (ConstantOP) co;
			if (cp.getQueryCocnept().isTop()) {
				opt = true;
			}
		}
		ScanOP si = null;
		if (opt) {
			// S1 (top and ...) -> S1(...)
			si = new ScanOP(inner, left.getSecondaryIndex());
		} else {
			si = new ScanOP(new JoinOP(left.getSubquery(), inner),
					left.getSecondaryIndex());
		}
		return si;

	}

	private ScanOP nestedScanIntersected(AlgebraicOP op, DescriptionLogicsKB kb) {
		if (op instanceof JoinOP) {
			JoinOP ip = (JoinOP) op;
			if (ip.getFirstSubQuery() instanceof ScanOP
					&& ip.getSecondSubQuery() instanceof ScanOP) {
				return twoScans((ScanOP) ip.getFirstSubQuery(),
						(ScanOP) ip.getSecondSubQuery(), kb);
			} else if (ip.getFirstSubQuery() instanceof JoinOP
					&& ip.getSecondSubQuery() instanceof ScanOP) {
				JoinOP iip = (JoinOP) ip.getFirstSubQuery();
				ScanOP scan = (ScanOP) ip.getSecondSubQuery();
				ScanOP temp = nestedScanIntersected(iip, kb);
				if (temp == null) {
					return null;
				}
				return twoScans(scan, temp, kb);
			} else if (ip.getSecondSubQuery() instanceof JoinOP
					&& ip.getFirstSubQuery() instanceof ScanOP) {
				JoinOP iip = (JoinOP) ip.getSecondSubQuery();
				ScanOP scan = (ScanOP) ip.getFirstSubQuery();
				ScanOP temp = nestedScanIntersected(iip, kb);
				if (temp == null) {
					return null;
				}
				return twoScans(scan, temp, kb);
			} else {
				return null;
			}
		}
		return null;
	}

	// select s2 as inner scan
	private ScanOP twoScans(ScanOP s1, ScanOP s2, DescriptionLogicsKB kb) {
		ProjDesc rep = new PdConcept(KBVocabulary.CONCEPT_TOP);
		ProjectionOP inner = new ProjectionOP(rep, s2, kb);
		// inner.setPlanEvalKB(kb);
		AlgebraicOP co = s1.getSubquery();
		boolean opt = false;
		if (co instanceof ConstantOP) {
			ConstantOP cp = (ConstantOP) co;
			if (cp.getQueryCocnept().isTop()) {
				opt = true;
			}
		}
		ScanOP si = null;
		if (opt) {
			// S1 (top and ...) -> S1(...)
			si = new ScanOP(inner, s1.getSecondaryIndex());
		} else {
			si = new ScanOP(new JoinOP(s1.getSubquery(), inner),
					s1.getSecondaryIndex());
		}
		return si;
	}

	@Override
	public boolean applicable() {
		if (this.op instanceof ProjectionOP) {
			ProjectionOP jp = (ProjectionOP) op;
			if (jp.getSubquery() instanceof JoinOP) {
				JoinOP ip = (JoinOP) jp.getSubquery();
				if (ip.getFirstSubQuery() instanceof ScanOP
						|| ip.getSecondSubQuery() instanceof ScanOP) {
					return true;
				}
			}
		}
		return false;
	}

}
